//
//  NSObject+SLModel.m
//  SLModel
//
//  Created by S_Line on 2018/6/21.
//  Copyright © 2018年 S_Line. All rights reserved.
//

#import "NSObject+SLModel.h"
#import <objc/message.h>
#import "SLClassInfo.h"

#define force_inline __inline__ __attribute__((always_inline))

typedef NS_ENUM (NSUInteger, SLEncodingTypeNS) {
    SLEncodingTypeNSUnknown     = 0,
    SLEncodingTypeNSString,
    SLEncodingTypeNSMutableString,
    SLEncodingTypeNSValue,
    SLEncodingTypeNSNumber,
    SLEncodingTypeNSDecimalNumber,
    SLEncodingTypeNSData,
    SLEncodingTypeNSMutableData,
    SLEncodingTypeNSDate,
    SLEncodingTypeNSURL,
    SLEncodingTypeNSArray,
    SLEncodingTypeNSMutableArray,
    SLEncodingTypeNSDictionary,
    SLEncodingTypeNSMutableDictionary,
    SLEncodingTypeNSSet,
    SLEncodingTypeNSMutableSet
};

static force_inline SLEncodingTypeNS SLClassGetNSType(Class cls) {
    if (!cls) {
        return SLEncodingTypeNSUnknown;
    }
    
    if ([cls isSubclassOfClass:[NSMutableString class]]) {
        return SLEncodingTypeNSMutableString;
    }
    
    if ([cls isSubclassOfClass:[NSString class]]) {
        return SLEncodingTypeNSString;
    }
    
    if ([cls isSubclassOfClass:[NSDecimalNumber class]]) {
        return SLEncodingTypeNSDecimalNumber;
    }
    
    if ([cls isSubclassOfClass:[NSNumber class]]) {
        return SLEncodingTypeNSNumber;
    }
    
    if ([cls isSubclassOfClass:[NSValue class]]) {
        return SLEncodingTypeNSValue;
    }
    
    if ([cls isSubclassOfClass:[NSMutableData class]]) {
        return SLEncodingTypeNSMutableData;
    }
    
    if ([cls isSubclassOfClass:[NSData class]]) {
        return SLEncodingTypeNSData;
    }
    
    if ([cls isSubclassOfClass:[NSDate class]]) {
        return SLEncodingTypeNSDate;
    }
    
    if ([cls isSubclassOfClass:[NSURL class]]) {
        return SLEncodingTypeNSURL;
    }
    
    if ([cls isSubclassOfClass:[NSMutableArray class]]) {
        return SLEncodingTypeNSMutableArray;
    }
    
    if ([cls isSubclassOfClass:[NSArray class]]) {
        return SLEncodingTypeNSArray;
    }
    
    if ([cls isSubclassOfClass:[NSMutableDictionary class]]) {
        return SLEncodingTypeNSDictionary;
    }
    
    if ([cls isSubclassOfClass:[NSDictionary class]]) {
        return SLEncodingTypeNSDictionary;
    }
    
    if ([cls isSubclassOfClass:[NSMutableSet class]]) {
        return SLEncodingTypeNSMutableSet;
    }
    
    if ([cls isSubclassOfClass:[NSSet class]]) {
        return SLEncodingTypeNSSet;
    }
    
    return SLEncodingTypeNSUnknown;
}

static force_inline BOOL SLEncodingTypeIsCNumber(SLEncodingType type) {
    switch (type & SLEncodingTypeMask) {
        case SLEncodingTypeBool:
        case SLEncodingTypeInt8:
        case SLEncodingTypeUInt8:
        case SLEncodingTypeInt16:
        case SLEncodingTypeUInt16:
        case SLEncodingTypeInt32:
        case SLEncodingTypeUInt32:
        case SLEncodingTypeInt64:
        case SLEncodingTypeUInt64:
        case SLEncodingTypeFloat:
        case SLEncodingTypeDouble:
        case SLEncodingTypeLongDouble:
            return true;
            break;
        default:
            return false;
            break;
    }
}

// Parse a number value from 'id';
static force_inline NSNumber *SLNSNumberCreateFromID(__unsafe_unretained id value) {
    static NSCharacterSet *dot;
    static NSDictionary *dic;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        dot = [NSCharacterSet characterSetWithRange:NSMakeRange('.', 1)];
        dic = @{
                @"TRUE": @(true),
                @"True": @(true),
                @"true": @(true),
                @"FALSE": @(false),
                @"False": @(false),
                @"false": @(false),
                @"YES": @(true),
                @"Yes": @(true),
                @"yes": @(true),
                @"NO": @(false),
                @"No": @(false),
                @"no": @(false),
                @"NIL": (id)kCFNull,
                @"Nil": (id)kCFNull,
                @"nil": (id)kCFNull,
                @"NULL": (id)kCFNull,
                @"Null": (id)kCFNull,
                @"null": (id)kCFNull,
                @"(NULL)": (id)kCFNull,
                @"(Null)": (id)kCFNull,
                @"(null)": (id)kCFNull,
                @"<NULL>": (id)kCFNull,
                @"<Null>": (id)kCFNull,
                @"<null>": (id)kCFNull
                };
    });
    if (!value || value == (id)kCFNull) {
        return nil;
    }
    if ([value isKindOfClass:[NSNumber class]]) {
        return value;
    }
    if ([value isKindOfClass:[NSString class]]) {
        NSNumber *num = dic[value];
        if (num != nil) {
            if (num == (id)kCFNull) {
                return nil;
            }
            return num;
        }
        if ([(NSString *)value rangeOfCharacterFromSet:dot].location != NSNotFound) {
            const char *cstring =((NSString *)value).UTF8String;
            if (!cstring) {
                return nil;
            }
            double num = atof(cstring);
            if (isnan(num) || isinf(num)) {
                return nil;
            }
            return @(num);
        } else {
            const char *cstring = ((NSString *)value).UTF8String;
            if (!cstring) {
                return nil;
            }
            return @(atoll(cstring));
        }
    }
    
    return nil;
}

// Parse string to date
static force_inline NSDate *SLNSDateFromString(__unsafe_unretained NSString *string) {
    typedef NSDate *(^SLNSdateParseBlock)(NSString *string);
    #define kParserNum 34
    static SLNSdateParseBlock blocks[kParserNum + 1] = {0};
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        {
            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
            formatter.dateFormat = @"yyyy-MM-dd";
            blocks[10] = ^(NSString *string) {
                return [formatter dateFromString:string];
            };
        }
        {
            NSDateFormatter *formatter1 = [[NSDateFormatter alloc] init];
            formatter1.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
            formatter1.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
            formatter1.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss";
            
            NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init];
            formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
            formatter2.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
            formatter2.dateFormat = @"yyyy-MM-dd HH:mm:ss";
            
            NSDateFormatter *formatter3 = [[NSDateFormatter alloc] init];
            formatter3.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
            formatter3.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
            formatter3.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS";
            
            NSDateFormatter *formatter4 = [[NSDateFormatter alloc] init];
            formatter4.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
            formatter4.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
            formatter4.dateFormat = @"yyyy-MM-dd HH:mm:ss.SSS";
            
            blocks[19] = ^(NSString *string) {
                if ([string characterAtIndex:10] == 'T') {
                    return [formatter1 dateFromString:string];
                } else {
                    return [formatter2 dateFromString:string];
                }
            };
            
            blocks[23] = ^(NSString *string) {
                if ([string characterAtIndex:10] == 'T') {
                    return[formatter3 dateFromString:string];
                } else {
                    return[formatter4 dateFromString:string];
                }
            };
        }
        
        {
            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
            formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
            
            NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init];
            formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
            formatter2.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSSZ";
            
            blocks[20] = ^(NSString *string) {
                return [formatter dateFromString:string];
            };
            blocks[24] = ^(NSString *string) {
                return [formatter dateFromString:string] ?: [formatter2 dateFromString:string];
            };
            blocks[25] = ^(NSString *string) {
                return [formatter dateFromString:string];
            };
            blocks[28] = ^(NSString *string) {
                return [formatter2 dateFromString:string];
            };
            blocks[29] = ^(NSString *string) {
                return [formatter2 dateFromString:string];
            };
        }
        {
            NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
            formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
            formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy";
            
            NSDateFormatter *formatter2 = [[NSDateFormatter alloc] init];
            formatter2.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
            formatter2.dateFormat = @"EEE MMM dd HH:mm:ss.SSS Z yyyy";
            
            blocks[30] = ^(NSString *string) {
                return [formatter dateFromString:string];
            };
            blocks[30] = ^(NSString *string) {
                return [formatter dateFromString:string];
            };
        }
    });
    
    if (!string) {
        return nil;
    }
    if (string.length > kParserNum) {
        return nil;
    }
    SLNSdateParseBlock parser = blocks[string.length];
    if (!parser) {
        return nil;
    }
    
    return parser(string);
    #undef kParserNum
}

// Get the 'NSBlock' class
static force_inline Class SLNSBlockClass() {
    static Class cls;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        void (^block)(void) = ^{};
        cls = ((NSObject *)block).class;
        while (class_getSuperclass(cls) != [NSObject class]) {
            cls = class_getSuperclass(cls);
        }
    });
    
    return cls;
}

/**
 Get the ISO date formatter.
 
 ISO8601 format example:
 2010-07-09T16:13:30+12:00
 2011-01-11T11:11:11+0000
 2011-01-26T19:06:43Z
 
 length:20/24/25
 */
static force_inline NSDateFormatter *SLISODateFormatter() {
    static NSDateFormatter *formatter = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        formatter = [[NSDateFormatter alloc] init];
        formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_UI_POSIX"];
        formatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ssZ";
    });
    
    return formatter;
}

// Get the value with key paths from dictionary
// The dic should be NSDictionary, and the keyPath should not be nil.
static force_inline id SLValueForKeyPath(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *keyPaths) {
    id value = nil;
    
    for (NSUInteger i = 0, max = keyPaths.count; i < max; i++) {
        value = dic[keyPaths[i]];
        if (i + 1 < max) {
            if ([value isKindOfClass:[NSDictionary class]]) {
                dic = value;
            } else {
                return nil;
            }
        }
    }
    
    return value;
}

// Get the value with multi key (or key path) from dictionary
//The dic should be NSDictionary
static force_inline id SLValueForMultiKeys(__unsafe_unretained NSDictionary *dic, __unsafe_unretained NSArray *multiKeys) {
    id value = nil;
    
    for (NSString *key in multiKeys) {
        if ([key isKindOfClass:[NSString class]]) {
            value = dic[key];
            if (value) {
                break;
            }
        } else if ([key isKindOfClass:[NSArray class]]) {
            value = SLValueForKeyPath(dic, (NSArray *)key);
        }
    }
    
    return value;
}

@interface _SLModelPropertyMeta: NSObject {
    @package
    NSString *_name;                            ///< property's name
    SLEncodingType _type;                       ///< property's type
    SLEncodingTypeNS _nsType;                   ///< property's Foundation type
    BOOL _isCNumber;                            ///< is c number type
    Class _cls;                                 ///< property's class, or nil
    Class _genericCls;                          ///< container's generic class, or nil if there's no generic class
    SEL _getter;                                ///< getter, or nil if the instances cannot respond
    SEL _setter;                                ///< setter, or nil if the instances cannot respond
    BOOL _isKVCCompatible;                      ///< YES if it can access with key-value coding
    BOOL _isStructAvailableForKeyedArchiver;    ///< YES if the struct can encoded with keyed archiver/unarchiver
    BOOL _hasCustomClassFromDictionary;         ///< class/generic class implements +modelCustomClassForDictionary:
    
    /**
     property->key:         _mappedToKey:key        _mappedToKeyPath:nil            _mappedToKeyArray:nil
     property->keyPath:     _mappedToKey:keyPath    _mappedToKeyPath:keyPath(array) _mappedToKeyArray:nil
     property->keys:        _mappedToKey:keys[0]    _mappedToKeyPath:nil/keyPath    _mappedToKeyArray:keys(array)
     */
    NSString *_mappedToKey;                     ///< the key mapped to
    NSArray *_mappedToKeyPath;                  ///< the key path mapped to (nil if the name is not key path)
    NSArray *_mappedToKeyArray;                 ///< the key(NSString) or keyPath(NSArray) array (nil if not mapped to multiple keys)
    SLClassPropertyInfo *_info;                 ///< property's info
    _SLModelPropertyMeta *_next;                ///< next meta if there are multiple properties mapped to the same key.
}

@end

@implementation _SLModelPropertyMeta

+ (instancetype)metaWithClassInfo:(SLClassInfo *)classInfo propertyInfo:(SLClassPropertyInfo *)propertyInfo generic:(Class)generic {
    
    // support pseudo generic class with protocol name
    if (!generic && propertyInfo.property) {
        for (NSString *protocol in propertyInfo.protocols) {
            Class cls = objc_getClass(protocol.UTF8String);
            if (cls) {
                generic = cls;
                break;
            }
        }
    }
    
    _SLModelPropertyMeta *meta = [self new];
    meta->_name = propertyInfo.name;
    meta->_type = propertyInfo.type;
    meta->_info = propertyInfo;
    meta->_genericCls = generic;
    
    if ((meta->_type & SLEncodingTypeMask) == SLEncodingTypeObject) {
        meta->_nsType = SLClassGetNSType(propertyInfo.cls);
    } else {
        meta->_isCNumber = SLEncodingTypeIsCNumber(meta->_type);
    }
    
    if ((meta->_type & SLEncodingTypeMask) == SLEncodingTypeStruct) {
        
        static NSSet *types = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            NSMutableSet *set = [NSMutableSet new];
            // 32bit
            [set addObject:@"{CGSize=ff}"];
            [set addObject:@"{CGPoint=ff}"];
            [set addObject:@"{CGRect={CGPoint=ff}(CGSize=ff}}"];
            [set addObject:@"{CGAffineTransform=ffffff}"];
            [set addObject:@"{UIEdgeInsets=ffff}"];
            [set addObject:@"{UIOffset=ff}"];
            // 64 bit
            [set addObject:@"{CGSize=dd}"];
            [set addObject:@"{CGPoint=dd}"];
            [set addObject:@"{CGRect={CGPoint=dd}(CGSize=dd}}"];
            [set addObject:@"{CGAffineTransform=dddddd}"];
            [set addObject:@"{UIEdgeInsets=dddd}"];
            [set addObject:@"{UIOffset=dd}"];
            
            types = set;
        });
        if ([types containsObject:propertyInfo.typeEncoding]) {
            meta->_isStructAvailableForKeyedArchiver = true;
        }
    }
    meta->_cls = propertyInfo.cls;
    
    if (generic) {
        meta->_hasCustomClassFromDictionary = [generic respondsToSelector:@selector(modelCustomClassForDictionary:)];
    } else if (meta->_cls && meta->_nsType == SLEncodingTypeNSUnknown) {
        meta->_hasCustomClassFromDictionary = [meta->_cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
    }
    
    if (propertyInfo.getter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.getter]) {
            meta->_getter = propertyInfo.getter;
        }
    }
    if (propertyInfo.setter) {
        if ([classInfo.cls instancesRespondToSelector:propertyInfo.setter]) {
            meta->_setter = propertyInfo.setter;
        }
    }
    
    if (meta->_getter && meta->_setter) {
        switch (meta->_type & SLEncodingTypeMask) {
            case SLEncodingTypeBool:
            case SLEncodingTypeInt8:
            case SLEncodingTypeUInt8:
            case SLEncodingTypeInt16:
            case SLEncodingTypeUInt16:
            case SLEncodingTypeInt32:
            case SLEncodingTypeUInt32:
            case SLEncodingTypeInt64:
            case SLEncodingTypeUInt64:
            case SLEncodingTypeFloat:
            case SLEncodingTypeDouble:
            case SLEncodingTypeLongDouble:
            case SLEncodingTypeObject:
            case SLEncodingTypeClass:
            case SLEncodingTypeBlock:
            case SLEncodingTypeStruct:
            case SLEncodingTypeUnion:
                meta->_isKVCCompatible = true;
                break;
            default:
                break;
        }
    }
    
    return meta;
}

@end

@interface _SLModelMeta: NSObject {
    @package
    SLClassInfo *_classInfo;
    /// Key: mapped key and key path, Value: _SLModelPropertyMeta.
    NSDictionary *_mapper;
    /// Array<_SLModelPropertyMeta>, all property meta of this model.
    NSArray *_allPropertyMetas;
    /// Array<_SLModelPropertyMeta>, property meta which is mapped to a key path.
    NSArray *_keyPathPropertyMetas;
    /// Array<_SLModelPropertyMeta>, property meta which is mapped to multi keys.
    NSArray *_multiKeysPropertyMetas;
    /// The number of mapped key (and key path), same to _mapper.count.
    NSUInteger _keyMappedCount;
    /// Model class type.
    SLEncodingTypeNS _nsType;
    
    BOOL _hasCustomWillTransformFromDictionary;
    BOOL _hasCustomTransformFromDictionary;
    BOOL _hasCustomTransformToDictionary;
    BOOL _hasCustomClassFromDictionary;
}

@end

@implementation _SLModelMeta

- (instancetype)initWithClass:(Class)cls {
    SLClassInfo *classInfo = [SLClassInfo classInfoWithClass:cls];
    if (!classInfo) {
        return nil;
    }
    self = [super init];

    // Get black list
    NSSet *blacklist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyBlacklist)]) {
        NSArray *properties = [(id<SLModel>)cls modelPropertyBlacklist];
        if (properties) {
            blacklist = [NSSet setWithArray:properties];
        }
    }
    
    // Get white list
    NSSet *whitelist = nil;
    if ([cls respondsToSelector:@selector(modelPropertyWhitelist)]) {
        NSArray *properties = [(id<SLModel>)cls modelPropertyWhitelist];
        if (properties) {
            whitelist = [NSSet setWithArray:properties];
        }
    }
    
    // Get container property's generic class
    NSDictionary *genericMapper = nil;
    if ([cls respondsToSelector:@selector(modelContainerPropertyGenericClass)]) {
        genericMapper = [(id<SLModel>)cls modelContainerPropertyGenericClass];
        if (genericMapper) {
            NSMutableDictionary *tmp = [NSMutableDictionary new];
            [genericMapper enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
                if (![key isKindOfClass:[NSString class]]) {
                    return;
                }
                Class meta = object_getClass(obj);
                if (!meta) {
                    return;
                }
                if (class_isMetaClass(meta)) {
                    tmp[key] = obj;
                } else if ([obj isKindOfClass:[NSString class]]) {
                    Class cls = NSClassFromString(obj);
                    if (cls) {
                        tmp[key] = cls;
                    }
                }
            }];
            genericMapper = tmp;
        }
    }
    
    // Create all property metas
    NSMutableDictionary *allPropertyMetas = [NSMutableDictionary new];
    SLClassInfo *curClassInfo = classInfo;
    while (curClassInfo && curClassInfo.superCls != nil) {  // recursive parse super class, but ignore root class (NSObject/NSProxy)
        for (SLClassPropertyInfo *propertyInfo in curClassInfo.propertyInfos.allValues) {
            if (!propertyInfo.name) {
                continue;
            }
            if (blacklist && [blacklist containsObject:propertyInfo.name]) {
                continue;
            }
            if (whitelist && ![whitelist containsObject:propertyInfo.name]) {
                continue;
            }
            
            _SLModelPropertyMeta *propertyMeta = [_SLModelPropertyMeta metaWithClassInfo:classInfo propertyInfo:propertyInfo generic:genericMapper[propertyInfo.name]];
            if (!propertyMeta || !propertyMeta->_name) {
                continue;
            }
            if (!propertyMeta->_getter || !propertyMeta->_setter) {
                continue;
            }
            if (allPropertyMetas[propertyMeta->_name]) {
                continue;
            }
            allPropertyMetas[propertyMeta->_name] = propertyMeta;
        }
        curClassInfo = curClassInfo.superClassInfo;
    }
    if (allPropertyMetas.count) {
        _allPropertyMetas = allPropertyMetas.allValues.copy;
    }
    
    // create mapper
    NSMutableDictionary *mapper = [NSMutableDictionary new];
    NSMutableArray *keyPathPropertyMetas = [NSMutableArray new];
    NSMutableArray *multiKeysPropertyMetas = [NSMutableArray new];
    if ([cls respondsToSelector:@selector(modelCustomPropertyMapper)]) {
        NSDictionary *customMapper = [(id<SLModel>)cls modelCustomPropertyMapper];
        [customMapper enumerateKeysAndObjectsUsingBlock:^(NSString *propertyName, NSString *mappedToKey, BOOL * _Nonnull stop) {
            _SLModelPropertyMeta *propertyMeta = allPropertyMetas[propertyName];
            if (!propertyMeta) {
                return;
            }
            [allPropertyMetas removeObjectForKey:propertyName];
            
            if ([mappedToKey isKindOfClass:[NSString class]]) {
                if (mappedToKey.length == 0) {
                    return;
                }
                
                propertyMeta->_mappedToKey = mappedToKey;
                NSArray *keyPath = [mappedToKey componentsSeparatedByString:@"."];
                for (NSString *onePath in keyPath) {
                    if (onePath.length == 0) {
                        NSMutableArray *tmp = keyPath.mutableCopy;
                        [tmp removeObject:@""];
                        keyPath = tmp;
                        break;
                    }
                }
                if (keyPath.count > 1) {
                    propertyMeta->_mappedToKeyPath = keyPath;
                    [keyPathPropertyMetas addObject:propertyMeta];
                }
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
            } else if ([mappedToKey isKindOfClass:[NSArray class]]) {
                NSMutableArray *mappedToKeyArray = [NSMutableArray new];
                for (NSString *oneKey in (NSArray *)mappedToKey) {
                    if (![oneKey isKindOfClass:[NSString class]]) {
                        continue;
                    }
                    if (oneKey.length == 0) {
                        continue;
                    }
                    
                    NSArray *keyPath = [oneKey componentsSeparatedByString:@"."];
                    for (NSString *onePath in keyPath) {
                        if (onePath.length == 0) {
                            NSMutableArray *tmp = keyPath.mutableCopy;
                            [tmp removeObject:@""];
                            keyPath = tmp;
                            break;
                        }
                    }
                    
                    if (keyPath.count > 1) {
                        [mappedToKeyArray addObject:keyPath];
                    } else {
                        [mappedToKeyArray addObject:oneKey];
                    }
                    
                    if (!propertyMeta->_mappedToKey) {
                        propertyMeta->_mappedToKey = oneKey;
                        propertyMeta->_mappedToKeyPath = keyPath.count > 1 ? keyPath : nil;
                    }
                }
                
                if (!propertyMeta->_mappedToKey) {
                    return;
                }
                
                propertyMeta->_mappedToKeyArray = mappedToKeyArray;
                [multiKeysPropertyMetas addObject:propertyMeta];
                
                propertyMeta->_next = mapper[mappedToKey] ?: nil;
                mapper[mappedToKey] = propertyMeta;
            }
        }];
    }
    
    [allPropertyMetas enumerateKeysAndObjectsUsingBlock:^(NSString *name, _SLModelPropertyMeta *propertyMeta, BOOL * _Nonnull stop) {
        propertyMeta->_mappedToKey = name;
        propertyMeta->_next = mapper[name] ?: nil;
        mapper[name] = propertyMeta;
    }];
    
    if (mapper.count) {
        _mapper = mapper;
    }
    if (keyPathPropertyMetas) {
        _keyPathPropertyMetas = keyPathPropertyMetas;
    }
    if (multiKeysPropertyMetas) {
        _multiKeysPropertyMetas = multiKeysPropertyMetas;
    }
    
    _classInfo = classInfo;
    _keyMappedCount = allPropertyMetas.count;
    _nsType = SLClassGetNSType(cls);
    _hasCustomWillTransformFromDictionary = [cls instancesRespondToSelector:@selector(modelCustomWillTransformFromDictionary:)];
    _hasCustomTransformFromDictionary = [cls instancesRespondToSelector:@selector(modelCustomTransformFromDictionary:)];
    _hasCustomTransformToDictionary = [cls instancesRespondToSelector:@selector(modelCustomTransformToDictionary:)];
    _hasCustomClassFromDictionary = [cls respondsToSelector:@selector(modelCustomClassForDictionary:)];
    
    return self;
}

/// Returns the cached model class meta
+ (instancetype)metaWithClass:(Class)cls {
    if (!cls) {
        return nil;
    }
    
    static CFMutableDictionaryRef cache;
    static dispatch_semaphore_t lock;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cache = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
        lock = dispatch_semaphore_create(1);
    });
    dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
    _SLModelMeta *meta = CFDictionaryGetValue(cache, (__bridge const void *)(cls));
    dispatch_semaphore_signal(lock);
    if (!meta || meta->_classInfo.needUpdate) {
        meta = [[_SLModelMeta alloc] initWithClass:cls];
        if (meta) {
            dispatch_semaphore_wait(lock, DISPATCH_TIME_FOREVER);
            CFDictionarySetValue(cache, (__bridge const void *)(cls), (__bridge const void *)(meta));
            dispatch_semaphore_signal(lock);
        }
    }
    
    return meta;
}

@end

/**
 Get number from property.
 @discussion Caller should hold strong reference to the parameters before this function returns.
 @param model Should not be nil.
 @param meta Should not be nil, meta.isCNumber should be YES, meta.getter should not be nil.
 @return A number object, or nil if failed.
 */
static force_inline NSNumber *ModelCreateNumberFromProperty(__unsafe_unretained id model, __unsafe_unretained _SLModelPropertyMeta *meta) {
    NSNumber *result;
    switch (meta->_type & SLEncodingTypeMask) {
        case SLEncodingTypeBool:
            result = @(((bool (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeInt8:
            result = @(((int8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeUInt8:
            result = @(((uint8_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeInt16:
            result = @(((int16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeUInt16:
            result = @(((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeInt32:
            result = @(((int32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeUInt32:
            result = @(((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeInt64:
            result = @(((int64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeUInt64:
            result = @(((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter));
            break;
        case SLEncodingTypeFloat:{
            float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
            if (isnan(num) || isinf(num)) {
            } else {
                result = @(num);
            }
        }
            break;
        case SLEncodingTypeDouble:{
            double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
            if (isnan(num) || isinf(num)) {
            } else {
                result = @(num);
            }
        }
            break;
        case SLEncodingTypeLongDouble:{
            double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
            if (isnan(num) || isinf(num)) {
            } else {
                result = @(num);
            }
        }
            break;
        default:
            break;
    }
    return result;
}

/**
 Set number to property.
 @discussion Caller should hold strong reference to the parameters before this function returns.
 @param model Should not be nil.
 @param num Can be nil.
 @param meta Should not be nil, meta.isCNumber should be YES, meta.setter should not be nil.
 */
static force_inline void ModelSetNumberToProperty(__unsafe_unretained id model, __unsafe_unretained NSNumber *num, __unsafe_unretained _SLModelPropertyMeta *meta) {
    switch (meta->_type & SLEncodingTypeMask) {
        case SLEncodingTypeBool:
            ((void (*)(id, SEL, BOOL))(void *) objc_msgSend)((id)model, meta->_setter, num.boolValue);
            break;
        case SLEncodingTypeInt8:
            ((void (*)(id, SEL, int8_t))(void *) objc_msgSend)((id)model, meta->_setter, (int8_t)num.charValue);
            break;
        case SLEncodingTypeUInt8:
            ((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint8_t)num.unsignedCharValue);
            break;
        case SLEncodingTypeInt16:
            ((void (*)(id, SEL, int16_t))(void *) objc_msgSend)((id)model, meta->_setter, (int16_t)num.shortValue);
            break;
        case SLEncodingTypeUInt16:
            ((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint16_t)num.unsignedShortValue);
            break;
        case SLEncodingTypeInt32:
            ((void (*)(id, SEL, int32_t))(void *) objc_msgSend)((id)model, meta->_setter, (int32_t)num.intValue);
            break;
        case SLEncodingTypeUInt32:
            ((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint32_t)num.unsignedIntValue);
            break;
        case SLEncodingTypeInt64:
            if ([num isKindOfClass:[NSDecimalNumber class]]) {
                ((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.longLongValue);
            } else {
                ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.unsignedLongLongValue);
            }
            break;
        case SLEncodingTypeUInt64:
            if ([num isKindOfClass:[NSDecimalNumber class]]) {
                ((void (*)(id, SEL, int64_t))(void *) objc_msgSend)((id)model, meta->_setter, (int64_t)num.longLongValue);
            } else {
                ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)model, meta->_setter, (uint64_t)num.unsignedLongLongValue);
            }
            break;
        case SLEncodingTypeFloat:{
            float f = num.floatValue;
            if (isnan(f) || isinf(f)) {
                f = 0;
            }
            ((void (*)(id, SEL, float))(void *) objc_msgSend)((id)model, meta->_setter, f);
        }
            break;
        case SLEncodingTypeDouble:{
            float d = num.doubleValue;
            if (isnan(d) || isinf(d)) {
                d = 0;
            }
            ((void (*)(id, SEL, double))(void *) objc_msgSend)((id)model, meta->_setter, d);
        }
            break;
        case SLEncodingTypeLongDouble:{
            float d = num.doubleValue;
            if (isnan(d) || isinf(d)) {
                d = 0;
            }
            ((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)model, meta->_setter, (long double)d);
        }
            break;
        default:
            break;
    }
}

/**
 Set value to model with a property meta.
 
 @discussion Caller should hold strong reference to the parameters before this function returns.
 
 @param model Should not be nil.
 @param value Should not be nil, buy can be NSNull.
 @param meta  Should not be nil, and meta->_setter should not be nil.
 */
static void ModelSetValueForProperty(__unsafe_unretained id model, __unsafe_unretained id value, __unsafe_unretained _SLModelPropertyMeta *meta) {
    if (meta->_isCNumber) {
        NSNumber *num = SLNSNumberCreateFromID(value);
        ModelSetNumberToProperty(model, num, meta);
        if (num != nil) {   // hold the number???
            [num class];
        }
    } else if (meta->_nsType) {
        switch (meta->_nsType) {
            case SLEncodingTypeNSString:
            case SLEncodingTypeNSMutableString:{
                if ([value isKindOfClass:[NSString class]]) {
                    if (meta->_nsType == SLEncodingTypeNSString) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                    } else {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id) model, meta->_setter, ((NSString *)value).mutableCopy);
                    }
                } else if ([value isKindOfClass:[NSNumber class]]) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((meta->_nsType == SLEncodingTypeNSString) ? ((NSNumber *)value).stringValue : ((NSNumber *)value).stringValue.mutableCopy));
                } else if ([value isKindOfClass:[NSData class]]) {
                    NSMutableString *string = [[NSMutableString alloc] initWithData:value encoding:NSUTF8StringEncoding];
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, string);
                } else if ([value isKindOfClass:[NSURL class]]) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((meta->_isCNumber == SLEncodingTypeNSString) ? ((NSURL *)value).absoluteString : ((NSURL *)value).absoluteString.mutableCopy));
                } else if ([value isKindOfClass:[NSAttributedString class]]) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((meta->_nsType == SLEncodingTypeNSString) ? ((NSAttributedString *)value).string : ((NSAttributedString *)value).string.mutableCopy));
                }
            }
                break;
            case SLEncodingTypeNSValue:
            case SLEncodingTypeNSNumber:
            case SLEncodingTypeNSDecimalNumber:{
                if (meta->_nsType == SLEncodingTypeNSString) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, SLNSNumberCreateFromID(value));
                } else if (meta->_nsType == SLEncodingTypeNSDecimalNumber) {
                    if ([value isKindOfClass:[NSDecimalNumber class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                    } else if ([value isKindOfClass:[NSNumber class]]) {
                        NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithDecimal:[((NSNumber *)value) decimalValue]];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
                    } else if ([value isKindOfClass:[NSString class]]) {
                        NSDecimalNumber *decNum = [NSDecimalNumber decimalNumberWithString:value];
                        NSDecimal dec = decNum.decimalValue;
                        if (dec._length == 0 && dec._isNegative) {
                            decNum = nil; //NaN
                        }
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, decNum);
                    }
                } else {            // SLEncodingTypeNSValue
                    if ([value isKindOfClass:[NSValue class]]) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                    }
                }
            }
                break;
            case SLEncodingTypeNSData:
            case SLEncodingTypeNSMutableData:{
                if ([value isKindOfClass:[NSData class]]) {
                    if (meta->_nsType == SLEncodingTypeNSData) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                    } else {
                        NSMutableData *data = ((NSData *)value).mutableCopy;
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
                    }
                } else if ([value isKindOfClass:[NSString class]]) {
                    NSData *data = [(NSString *)value dataUsingEncoding:NSUTF8StringEncoding];
                    if (meta->_nsType == SLEncodingTypeNSMutableData) {
                        data = data.mutableCopy;
                    }
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, data);
                }
            }
                break;
            case SLEncodingTypeNSDate:{
                if ([value isKindOfClass:[NSDate class]]) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                } else if ([value isKindOfClass:[NSString class]]) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, SLNSDateFromString(value));
                }
            }
            case SLEncodingTypeNSURL:{
                if ([value isKindOfClass:[NSURL class]]) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                } else if ([value isKindOfClass:[NSString class]]) {
                    NSCharacterSet *set = [NSCharacterSet whitespaceAndNewlineCharacterSet];
                    NSString *str = [value stringByTrimmingCharactersInSet:set];
                    if (str.length == 0) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, nil);
                    } else {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, [[NSURL alloc] initWithString:str]);
                    }
                }
            }
                break;
            case SLEncodingTypeNSArray:
            case SLEncodingTypeNSMutableArray:{
                if (meta->_genericCls) {
                    NSArray *valueArr = nil;
                    if ([value isKindOfClass:[NSArray class]]) {
                        valueArr = value;
                    } else if ([value isKindOfClass:[NSSet class]]) {
                        valueArr = ((NSSet *)value).allObjects;
                    }
                    if (valueArr) {
                        NSMutableArray *objectArr = [NSMutableArray new];
                        for (id one in valueArr) {
                            if ([one isKindOfClass:[NSDictionary class]]) {
                                Class cls = meta->_genericCls;
                                if (meta->_hasCustomClassFromDictionary) {
                                    cls = [cls modelCustomClassForDictionary:one];
                                    if (!cls) {
                                        cls = meta->_genericCls;
                                    }
                                }
                                NSObject *newOne = [cls new];
//                                [newOne ];
                                if (newOne) {
                                    [objectArr addObject:newOne];
                                }
                            }
                        }
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, objectArr);
                    }
                } else {
                    if ([value isKindOfClass:[NSArray class]]) {
                        if (meta->_nsType == SLEncodingTypeNSArray) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        } else {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSArray *)value).mutableCopy);
                        }
                    } else if ([value isKindOfClass:[NSSet class]]) {
                        if (meta->_nsType == SLEncodingTypeNSArray) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects);
                        } else {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSSet *)value).allObjects.mutableCopy);
                        }
                    }
                }
            }
                break;
            case SLEncodingTypeNSDictionary:
            case SLEncodingTypeNSMutableDictionary:{
                if ([value isKindOfClass:[NSDictionary class]]) {
                    if (meta->_genericCls) {
                        NSMutableDictionary *dic = [NSMutableDictionary new];
                        [((NSDictionary *)value) enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
                            if ([value isKindOfClass:[NSDictionary class]]) {
                                Class cls = meta->_genericCls;
                                if (meta->_hasCustomClassFromDictionary) {
                                    cls = [cls modelCustomClassForDictionary:value];
                                    if (!cls) {
                                        cls = meta->_genericCls;    // for xcode code coverage
                                    }
                                }
                                NSObject *newOne = [cls new];
//                                [newOne ];
                                if (newOne) {
                                    dic[key] = newOne;
                                }
                            }
                        }];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, dic);
                    } else {
                        if (meta->_nsType == SLEncodingTypeNSDictionary) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, value);
                        } else if (meta->_nsType == SLEncodingTypeNSMutableDictionary) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, ((NSDictionary *)value).mutableCopy);
                        }
                    }
                }
            }
                break;
            case SLEncodingTypeNSSet:
            case SLEncodingTypeNSMutableSet:{
                NSSet *valueSet = nil;
                if ([value isKindOfClass:[NSArray class]]) {
                    valueSet = [NSMutableSet setWithArray:value];
                } else if ([value isKindOfClass:[NSSet class]]) {
                    valueSet = ((NSSet *)value);
                }
                
                if (meta->_genericCls) {
                    NSMutableSet *set = [NSMutableSet new];
                    for (id one in valueSet) {
                        if ([one isKindOfClass:meta->_genericCls]) {
                            [set addObject:one];
                        } else if ([one isKindOfClass:[NSDictionary class]]) {
                            Class cls = meta->_genericCls;
                            if (meta->_hasCustomClassFromDictionary) {
                                cls = [cls modelCustomClassForDictionary:one];
                                if (!cls) {
                                    cls = meta->_genericCls;
                                }
                            }
                            NSObject *newOne = [cls new];
//                            [new ];
                            if (newOne) {
                                [set addObject:newOne];
                            }
                        }
                    }
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, set);
                } else {
                    if (meta->_nsType == SLEncodingTypeNSSet) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet);
                    } else if (meta->_nsType == SLEncodingTypeNSMutableSet) {
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, valueSet.mutableCopy);
                    }
                }
            }       // commented for code coverage in next line
                break;
            default:
                break;
        }
    } else {
        BOOL isNull = (value == (id)kCFNull);
        switch (meta->_nsType & SLEncodingTypeMask) {
            case SLEncodingTypeObject:{
                Class cls = meta->_genericCls ?: meta->_cls;
                if (isNull) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)nil);
                } else if ([value isKindOfClass:cls]) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (id)value);
                } else if ([value isKindOfClass:[NSDictionary class]]) {
                    NSObject *one = nil;
                    if (meta->_getter) {
                        one = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, meta->_getter);
                    }
                    if (one) {
//                        [one ]
                    } else {
                        if (meta->_hasCustomClassFromDictionary) {
                            cls = [cls modelCustomClassForDictionary: value] ?: cls;
                        }
                        one = [cls new];
//                        [one ];
                        ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, one);
                    }
                }
            }
                break;
            case SLEncodingTypeClass:{
                if (isNull) {
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, (Class)NULL);
                } else {
                    Class cls = nil;
                    if ([value isKindOfClass:[NSString class]]) {
                        cls = NSClassFromString(value);
                        if (cls) {
                            ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, cls);
                        }
                    } else {
                        cls = object_getClass(value);
                        if (cls) {
                            if (class_isMetaClass(cls)) {
                                ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)model, meta->_setter, cls);
                            }
                        }
                    }
                }
            }
                break;
            case SLEncodingTypeSel:{
                if (isNull) {
                    ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)NULL);
                } else if ([value isKindOfClass:[NSString class]]) {
                    SEL sel = NSSelectorFromString(value);
                    ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)model, meta->_setter, (SEL)sel);
                }
            }
                break;
            case SLEncodingTypeBlock:{
                if (isNull) {
                    ((void (*)(id, SEL, void(^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void(^)())NULL);
                } else if ([value isKindOfClass:SLNSBlockClass()]) {
                    ((void (*)(id, SEL, void(^)()))(void *) objc_msgSend)((id)model, meta->_setter, (void(^)())value);
                }
            }
                break;
            case SLEncodingTypeStruct:
            case SLEncodingTypeUnion:
            case SLEncodingTypeCArray:{
                if ([value isKindOfClass:[NSValue class]]) {
                    const char *valueType = ((NSValue *)value).objCType;
                    const char *metaType = meta->_info.typeEncoding.UTF8String;
                    if (valueType && metaType && strcmp(valueType, metaType) == 0) {
                        [model setValue:value forKey:meta->_name];
                    }
                }
            }
                break;
            case SLEncodingTypePointer:
            case SLEncodingTypeCString:{
                if (isNull) {
                    ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, (void *)NULL);
                } else if ([value isKindOfClass:[NSValue class]]) {
                    NSValue *nsValue = value;
                    ((void (*)(id, SEL, void *))(void *) objc_msgSend)((id)model, meta->_setter, nsValue.pointerValue);
                }
            }
                break;
            default:
                break;
        }
    }
}

typedef struct {
    void *modelMeta;    ///< _SLModelMeta
    void *model;        ///< id(self)
    void *dictionary;   ///< NSDictionary(json)
} ModelSetContext;

/**
 Apply function for dictionary, to set the key-value pair to model.
 
 @param _key        should not be nil, NSString.
 @param _value      should not be nil.
 @param _context    _context.modelMeta and _context.model should not be nil.
 */
static void ModelSetWithDictionaryFunction(const void *_key, const void *_value, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained _SLModelMeta *meta = (__bridge _SLModelMeta *)(context->modelMeta);
    __unsafe_unretained _SLModelPropertyMeta *propertyMeta = [meta->_mapper objectForKey:(__bridge id)(_key)];
    __unsafe_unretained id model = (__bridge id)(context->model);
    while (propertyMeta) {
        if (propertyMeta->_setter) {
            ModelSetValueForProperty(model, (__bridge __unsafe_unretained id)_value, propertyMeta);
        }
        propertyMeta = propertyMeta->_next;
    }
}

/**
 Apply function for model property meta, to set dictionary to model.
 
 @param _propertyMeta should not be nil, _SLModelPropertyMeta.
 @param _context      _context.model and _context.dictionary should not be nil.
 */
static void ModelSetWithPropertyMetaArrayFunction(const void *_propertyMeta, void *_context) {
    ModelSetContext *context = _context;
    __unsafe_unretained NSDictionary *dictionary = (__bridge NSDictionary *)(context->dictionary);
    __unsafe_unretained _SLModelPropertyMeta *propertyMeta = (__bridge _SLModelPropertyMeta *)_propertyMeta;
    if (!propertyMeta->_setter) {
        return;
    }
    id value = nil;
    if (propertyMeta->_mappedToKeyArray) {
        value = SLValueForMultiKeys(dictionary, propertyMeta->_mappedToKeyArray);
    } else if (propertyMeta->_mappedToKeyPath) {
        value = SLValueForKeyPath(dictionary, propertyMeta->_mappedToKeyPath);
    } else {
        value = [dictionary objectForKey:propertyMeta->_mappedToKey];
    }
    if (value) {
        __unsafe_unretained id model = (__bridge id)(context->model);
        ModelSetValueForProperty(model, value, propertyMeta);
    }
}

/**
 Returns a valid JSON object (NSArray/NSDictionary/NSString/NSNumber/NSNull), or nil if an error occurs.
 
 @param model Model, can be nil.
 @return JSON object, nil if an error occurs.
 */
static id ModelToJsonObjectRecursive(NSObject *model) {
    if (!model || model == (id)kCFNull) {
        return model;
    }
    if ([model isKindOfClass:[NSString class]]) {
        return model;
    }
    if ([model isKindOfClass:[NSNumber class]]) {
        return model;
    }
    if ([model isKindOfClass:[NSDictionary class]]) {
        if ([NSJSONSerialization isValidJSONObject:model]) {
            return model;
        }
        NSMutableDictionary *newDic = [NSMutableDictionary new];
        [((NSDictionary *)model) enumerateKeysAndObjectsUsingBlock:^(NSString *key, id  _Nonnull obj, BOOL * _Nonnull stop) {
            NSString *stringKey = [key isKindOfClass:[NSString class]] ? key : key.description;
            if (!stringKey) {
                return;
            }
            id jsonObj = ModelToJsonObjectRecursive(obj);
            if (!jsonObj) {
                jsonObj = (id)kCFNull;
            }
            newDic[stringKey] = jsonObj;
        }];
        
        return newDic;
    }
    
    if ([model isKindOfClass:[NSSet class]]) {
        NSArray *array = ((NSSet *)model).allObjects;
        if ([NSJSONSerialization isValidJSONObject:array]) {
            return array;
        }
        NSMutableArray *newArray = [NSMutableArray new];
        for (id obj in array) {
            if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
                [newArray addObject:obj];
            } else {
                id jsonObj = ModelToJsonObjectRecursive(obj);
                if (jsonObj && jsonObj != (id)kCFNull) {
                    [newArray addObject:jsonObj];
                }
            }
        }
        
        return newArray;
    }
    
    if ([model isKindOfClass:[NSArray class]]) {
        if ([NSJSONSerialization isValidJSONObject:model]) {
            return model;
        }
        NSMutableArray *newArray = [NSMutableArray new];
        for (id obj in (NSArray *)model) {
            if ([obj isKindOfClass:[NSString class]] || [obj isKindOfClass:[NSNumber class]]) {
                [newArray addObject:obj];
            } else {
                id jsonObj = ModelToJsonObjectRecursive(obj);
                if (jsonObj && jsonObj != (id)kCFNull) {
                    [newArray addObject:jsonObj];
                }
            }
        }
        
        return newArray;
    }
    
    if ([model isKindOfClass:[NSURL class]]) {
        return ((NSURL *)model).absoluteString;
    }
    
    if ([model isKindOfClass:[NSAttributedString class]]) {
        return ((NSAttributedString *)model).string;
    }
    
    if ([model isKindOfClass:[NSDate class]]) {
        return [SLISODateFormatter() stringFromDate:(id)model];
    }
    
    if ([model isKindOfClass:[NSData class]]) {
        return nil;
    }
    
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:[model class]];
    if (!modelMeta || modelMeta->_keyMappedCount == 0) {
        return nil;
    }
    
    NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithCapacity:64];
    __unsafe_unretained NSMutableDictionary *dic = result; // avoid retain and release in block
    [modelMeta->_mapper enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
        _SLModelPropertyMeta *propertyMeta = obj;
        
        if (!propertyMeta->_getter) {
            return ;
        }
        
        id value = nil;
        if (propertyMeta->_isCNumber) {
            value = ModelCreateNumberFromProperty(model, propertyMeta);
        } else if (propertyMeta->_nsType) {
            id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
            value = ModelToJsonObjectRecursive(v);
        } else {
            switch (propertyMeta->_type & SLEncodingTypeMask) {
                case SLEncodingTypeObject:{
                    id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = ModelToJsonObjectRecursive(v);
                }
                    break;
                case SLEncodingTypeClass:{
                    Class v = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = v ? NSStringFromClass(v) : nil;
                }
                    break;
                case SLEncodingTypeSel:{
                    SEL v = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                    value = v ? NSStringFromSelector(v) : nil;
                }
                    break;
                default:
                    break;
            }
        }
        
        if (!value) {
            return;
        }
        
        if (propertyMeta->_mappedToKeyPath) {
            NSMutableDictionary *superDic = dic;
            NSMutableDictionary *subDic = nil;
            for (NSUInteger i = 0, max = propertyMeta->_mappedToKeyPath.count; i < max; i++) {
                NSString *key = propertyMeta->_mappedToKeyPath[i];
                if (i + 1 == max) {
                    if (!superDic[key]) {
                        superDic[key] = value;
                    }
                    break;
                }
                
                subDic = superDic[key];
                if (subDic) {
                    if ([subDic isKindOfClass:[NSDictionary class]]) {
                        subDic = subDic.mutableCopy;
                        superDic[key] = subDic;
                    } else {
                        break;
                    }
                } else {
                    subDic = [NSMutableDictionary new];
                    superDic[key] = subDic;
                }
                superDic = subDic;
                subDic = nil;
            }
        } else {
            if (!dic[propertyMeta->_mappedToKey]) {
                dic[propertyMeta->_mappedToKey] = value;
            }
        }
    }];
    
    if (modelMeta ->_hasCustomTransformToDictionary) {
        BOOL suc = [((id<SLModel>)model) modelCustomTransformToDictionary:dic];
        if (!suc) {
            return nil;
        }
    }
    
    return result;
}

/// Add indent to string (exclude first line)
static NSMutableString *ModelDescriptionAddIndent(NSMutableString *desc, NSUInteger indent) {
    for (NSUInteger i = 0, max = desc.length; i < max; i++) {
        unichar c = [desc characterAtIndex:i];
        if (c == '\n') {
            for (NSUInteger j = 0; j < indent; j++) {
                [desc insertString:@"    " atIndex:i + 1];
            }
            
            i += indent * 4;
            max += indent * 4;
        }
    }
    return desc;
}

static NSString *ModelDescription(NSObject *model) {
    static const int kDescMaxLength = 100;
    if (!model) {
        return @"<nil>";
    }
    if (model == (id)kCFNull) {
        return @"<null>";
    }
    if (![model isKindOfClass:[NSObject class]]) {
        return [NSString stringWithFormat:@"%@", model];
    }
    
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:model.class];
    switch (modelMeta->_nsType) {
        case SLEncodingTypeNSString:
        case SLEncodingTypeNSMutableString:{
            return [NSString stringWithFormat:@"\"%@\"", model];
        }
            break;
        case SLEncodingTypeNSValue:
        case SLEncodingTypeNSData:
        case SLEncodingTypeNSMutableData:{
            NSString *tmp = model.description;
            if (tmp.length > kDescMaxLength) {
                tmp = [tmp substringToIndex:kDescMaxLength];
                tmp = [tmp stringByAppendingString:@"..."];
            }
            return tmp;
        }
            break;
        case SLEncodingTypeNSNumber:
        case SLEncodingTypeNSDecimalNumber:
        case SLEncodingTypeNSDate:
        case SLEncodingTypeNSURL:{
            return [NSString stringWithFormat:@"%@", model];
        }
            break;
        case SLEncodingTypeNSSet:
        case SLEncodingTypeNSMutableSet:{
            model = ((NSSet *)model).allObjects;
        }
        case SLEncodingTypeNSArray:
        case SLEncodingTypeNSMutableArray:{
            NSArray *array = (id)model;
            NSMutableString *desc = [NSMutableString new];
            if (array.count == 0) {
                return [desc stringByAppendingString:@"[]"];
            } else {
                [desc appendFormat:@"[\n"];
                for (NSUInteger i = 0, max = array.count; i < max; i++) {
                    NSObject *obj = array[i];
                    [desc appendString:@"   "];
                    [desc appendString:ModelDescriptionAddIndent(ModelDescription(obj).mutableCopy, 1)];
                    [desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
                }
                [desc appendString:@"]"];
                return desc;
            }
        }
            break;
        case SLEncodingTypeNSDictionary:
        case SLEncodingTypeNSMutableDictionary:{
            NSDictionary *dic = (id)model;
            NSMutableString *desc = [NSMutableString new];
            if (dic.count == 0) {
                return [desc stringByAppendingString:@"{}"];
            } else {
                NSArray *keys = dic.allKeys;
                
                [desc appendString:@"{\n"];
                for (NSUInteger i = 0, max = keys.count; i < max; i++) {
                    NSString *key = keys[i];
                    NSObject *value = dic[key];
                    [desc appendString:@"   "];
                    [desc appendFormat:@"%@ = %@", key, ModelDescriptionAddIndent(ModelDescription(value).mutableCopy, 1)];
                    [desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
                }
                [desc appendString:@"}"];
            }
            return desc;
        }
            break;
        default:{
            NSMutableString *desc = [NSMutableString new];
            [desc appendFormat:@"<%@: %p", model.class, model];
            if (modelMeta->_allPropertyMetas.count == 0) {
                return desc;
            }
            
            // sort property names
            NSArray *properties = [modelMeta->_allPropertyMetas sortedArrayUsingComparator:^NSComparisonResult(_SLModelPropertyMeta *p1, _SLModelPropertyMeta *p2) {
                return [p1->_name compare:p2->_name];
            }];
            
            [desc appendString:@" {\n"];
            
            for (NSUInteger i = 0, max = properties.count; i < max; i++) {
                _SLModelPropertyMeta *propertyMeta = properties[i];
                NSString *propertyDesc;
                if (propertyMeta->_isCNumber) {
                    NSNumber *num = ModelCreateNumberFromProperty(model, propertyMeta);
                    propertyDesc = num.stringValue;
                } else {
                    switch (propertyMeta->_type & SLEncodingTypeMask) {
                        case SLEncodingTypeObject:{
                            id v = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                            propertyDesc = ModelDescription(v);
                            if (!propertyDesc) {
                                propertyDesc = @"<nil>";
                            }
                        }
                            break;
                        case SLEncodingTypeClass:{
                            Class cls = ((Class (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                            if (cls) {
                                propertyDesc = NSStringFromClass(cls);
                            }
                            if (!propertyDesc) {
                                propertyDesc = @"<nil>";
                            }
                        }
                            break;
                        case SLEncodingTypeSel:{
                            SEL sel = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                            if (sel) {
                                propertyDesc = NSStringFromSelector(sel);
                            }
                            if (!propertyDesc) {
                                propertyDesc = @"<NULL>";
                            }
                        }
                            break;
                        case SLEncodingTypeBlock:{
                            id block = ((id (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                            propertyDesc = block ? ((NSOrderedSet *)block).description : @"<nil>";
                        }
                            break;
                        case SLEncodingTypeCArray:
                        case SLEncodingTypeCString:
                        case SLEncodingTypePointer:{
                            void *pointer = ((void * (*)(id, SEL))(void *) objc_msgSend)((id)model, propertyMeta->_getter);
                            propertyDesc = [NSString stringWithFormat:@"%p", pointer];
                        }
                            break;
                        case SLEncodingTypeStruct:
                        case SLEncodingTypeUnion:{
                            NSValue *value = [model valueForKey:propertyMeta->_name];
                            propertyDesc = value ? value.description : @"<unknown>";
                        }
                            break;
                        default:
                            propertyDesc = @"<unknown>";
                            break;
                    }
                }
                
                propertyDesc = ModelDescriptionAddIndent(propertyDesc.mutableCopy, 1);
                [desc appendFormat:@"   %@ = %@", propertyMeta->_name, propertyDesc];
                [desc appendString:(i + 1 == max) ? @"\n" : @";\n"];
            }
            
            [desc appendString:@"}"];
            return desc;
        }
            break;
    }
}

@implementation NSObject (SLModel)

+ (NSDictionary *)_sl_dictionaryWithJson:(id)json {
    if (!json || json == (id)kCFNull) {
        return nil;
    }
    NSDictionary *dic = nil;
    NSData *jsonData = nil;
    if ([json isKindOfClass:[NSDictionary class]]) {
        dic = json;
    } else if ([json isKindOfClass:NSString.class]) {
        jsonData = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
    } else if ([json isKindOfClass:NSData.class]) {
        jsonData = json;
    }
    
    if (jsonData) {
        dic = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
        if (![dic isKindOfClass:NSDictionary.class]) {
            dic = nil;
        }
    }
    
    return dic;
}

+ (instancetype)sl_modelWithJSON:(id)json {
    NSDictionary *dic = [self _sl_dictionaryWithJson:json];
    return [self sl_modelWithDictionary:dic];
}

+ (instancetype)sl_modelWithDictionary:(NSDictionary *)dictionary {
    if (!dictionary || dictionary == (id)kCFNull) {
        return nil;
    }
    if (![dictionary isKindOfClass:NSDictionary.class]) {
        return nil;
    }
    
    Class cls = [self class];
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:cls];
    if (modelMeta->_hasCustomClassFromDictionary) {
        cls = [cls modelCustomClassForDictionary:dictionary] ?: cls;
    }
    
    NSObject *one = [cls new];
    if ([one sl_modelSetWithDictionary:dictionary]) {
        return one;
    }
    
    return nil;
}

- (BOOL)sl_modelSetWithJSON:(id)json {
    NSDictionary *dic = [NSObject _sl_dictionaryWithJson:json];
    return [self sl_modelSetWithDictionary:dic];
}

- (BOOL)sl_modelSetWithDictionary:(NSDictionary *)dictionary {
    NSDictionary *dic = dictionary;
    if (!dic || dic == (id)kCFNull) {
        return NO;
    }
    if (![dic isKindOfClass:[NSDictionary class]]) {
        return NO;
    }
    
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:object_getClass(self)];
    if (modelMeta->_keyMappedCount == 0) {
        return NO;
    }
    
    if (modelMeta->_hasCustomWillTransformFromDictionary) {
        dic = [(id<SLModel>)self modelCustomWillTransformFromDictionary:dic];
        if (![dic isKindOfClass:[NSDictionary class]]) {
            return NO;
        }
    }
    
    ModelSetContext context = {0};
    context.modelMeta = (__bridge void *)modelMeta;
    context.model = (__bridge void *)self;
    context.dictionary = (__bridge void *)dic;
    
    if (modelMeta->_keyMappedCount >= CFDictionaryGetCount((CFDictionaryRef)dic)) {
        CFDictionaryApplyFunction((CFDictionaryRef)dic, ModelSetWithDictionaryFunction, &context);
        if (modelMeta->_keyPathPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_keyPathPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_keyPathPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context);
        }
        if (modelMeta->_multiKeysPropertyMetas) {
            CFArrayApplyFunction((CFArrayRef)modelMeta->_multiKeysPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_multiKeysPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context);
        }
    } else {
        CFArrayApplyFunction((CFArrayRef)modelMeta->_allPropertyMetas, CFRangeMake(0, CFArrayGetCount((CFArrayRef)modelMeta->_allPropertyMetas)), ModelSetWithPropertyMetaArrayFunction, &context);
    }
    
    if (modelMeta->_hasCustomTransformFromDictionary) {
        return [(id<SLModel>)self modelCustomTransformFromDictionary:dic];
    }
    
    return YES;
}

- (id)sl_modelToJSONObject {
    /*
     Apple said:
     The top level object is an NSArray or NSDictionary.
     All objects are instances of NSString, NSNumber, NSArray, NSDictionary, or NSNull.
     ALL dictionary keys are instances of NSString.
     Numbers are not NaN or infinity.
     */
    id jsonObject = ModelToJsonObjectRecursive(self);
    if ([jsonObject isKindOfClass:[NSArray class]] || [jsonObject isKindOfClass:[NSDictionary class]]) {
        return jsonObject;
    }
    return nil;
}

- (NSData *)sl_modelToJSONData {
    id jsonObject = [self sl_modelToJSONObject];
    if (!jsonObject) {
        return nil;
    }
    
    return [NSJSONSerialization dataWithJSONObject:jsonObject options:0 error:NULL];
}

- (NSString *)sl_modelToJSONString {
    NSData *jsonData = [self sl_modelToJSONData];
    if (jsonData.length == 0) {
        return nil;
    }
    
    return [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
}

- (id)sl_modelCopy {
    if (self == (id)kCFNull) {
        return self;
    }
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:self.class];
    if (modelMeta->_nsType) {
        return [self copy];
    }
    
    NSObject *one = [self.class new];
    for (_SLModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
        if (!propertyMeta->_getter || !propertyMeta->_setter) {
            continue;
        }
        
        if (propertyMeta->_isCNumber) {
            switch (propertyMeta->_type & SLEncodingTypeMask) {
                case SLEncodingTypeBool:{
                    bool num = ((bool (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, bool))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
                }
                    break;
                case SLEncodingTypeInt8:
                case SLEncodingTypeUInt8:{
                    uint8_t num = ((uint8_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, uint8_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
                }
                    break;
                case SLEncodingTypeInt16:
                case SLEncodingTypeUInt16:{
                    uint16_t num = ((uint16_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, uint16_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
                }
                    break;
                case SLEncodingTypeInt32:
                case SLEncodingTypeUInt32:{
                    uint32_t num = ((uint32_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, uint32_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
                }
                    break;
                case SLEncodingTypeInt64:
                case SLEncodingTypeUInt64:{
                    uint64_t num = ((uint64_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, uint64_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
                }
                    break;
                case SLEncodingTypeFloat:{
                    float num = ((float (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, float))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
                }
                    break;
                case SLEncodingTypeDouble:{
                    double num = ((double (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, double))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
                }
                    break;
                case SLEncodingTypeLongDouble:{
                    long double num = ((long double (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, long double))(void *) objc_msgSend)((id)one, propertyMeta->_setter, num);
                }
                    break;
                default:
                    break;
            }
        } else {
            switch (propertyMeta->_type & SLEncodingTypeMask) {
                case SLEncodingTypeObject:
                case SLEncodingTypeClass:
                case SLEncodingTypeBlock:{
                    id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value);
                }
                    break;
                case SLEncodingTypeSel:
                case SLEncodingTypePointer:
                case SLEncodingTypeCString:{
                    size_t value = ((size_t (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                    ((void (*)(id, SEL, size_t))(void *) objc_msgSend)((id)one, propertyMeta->_setter, value);
                }
                    break;
                case SLEncodingTypeStruct:
                case SLEncodingTypeUnion:{
                    @try {
                        NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
                        if (value) {
                            [one setValue:value forKey:propertyMeta->_name];
                        }
                    }
                    @catch (NSException *exception) {
                        
                    }
                }
                    break;
                default:
                    break;
            }
        }
    }
    
    return one;
}

- (void)sl_modelEncodeWithCoder:(NSCoder *)aCoder {
    if (!aCoder) {
        return;
    }
    
    if (self == (id)kCFNull) {
        [((id<NSCoding>)self) encodeWithCoder:aCoder];
        return;
    }
    
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:self.class];
    if (modelMeta->_nsType) {
        [(id<NSCoding>)self encodeWithCoder:aCoder];
        return;
    }
    
    for (_SLModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
        if (!propertyMeta->_getter) {
            return;
        }
        
        if (propertyMeta->_isCNumber) {
            NSNumber *value = ModelCreateNumberFromProperty(self, propertyMeta);
            if (value != nil) {
                [aCoder encodeObject:value forKey:propertyMeta->_name];
            } else {
                switch (propertyMeta->_type & SLEncodingTypeMask) {
                    case SLEncodingTypeObject:{
                        id value = ((id (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                        if (value && (propertyMeta->_nsType || [value respondsToSelector:@selector(encodeWithCoder:)])) {
                            if ([value isKindOfClass:[NSValue class]]) {
                                if ([value isKindOfClass:[NSNumber class]]) {
                                    [aCoder encodeObject:value forKey:propertyMeta->_name];
                                }
                            } else {
                                [aCoder encodeObject:value forKey:propertyMeta->_name];
                            }
                        }
                    }
                        break;
                    case SLEncodingTypeSel:{
                        SEL sel = ((SEL (*)(id, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_getter);
                        if (sel) {
                            NSString *str = NSStringFromSelector(sel);
                            [aCoder encodeObject:str forKey:propertyMeta->_name];
                        }
                    }
                        break;
                    case SLEncodingTypeStruct:
                    case SLEncodingTypeUnion:{
                        if (propertyMeta->_isKVCCompatible && propertyMeta->_isStructAvailableForKeyedArchiver) {
                            @try {
                                NSValue *value = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
                                [aCoder encodeObject:value forKey:propertyMeta->_name];
                            }
                            @catch (NSException *exception) {
                                
                            }
                        }
                    }
                        break;
                    default:
                        break;
                }
            }
        }
    }
}

- (id)sl_modelInitWithCoder:(NSCoder *)aDecoder {
    if (!aDecoder) {
        return self;
    }
    
    if (self == (id)kCFNull) {
        return self;
    }
    
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:self.class];
    if (modelMeta->_nsType) {
        return self;
    }
    
    for (_SLModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
        if (!propertyMeta->_nsType) {
            return self;
        }
        
        if (propertyMeta->_isCNumber) {
            NSNumber *value = [aDecoder decodeObjectForKey:propertyMeta->_name];
            if ([value isKindOfClass:[NSNumber class]]) {
                ModelSetNumberToProperty(self, value, propertyMeta);
                [value class];
            }
        } else {
            SLEncodingType type = propertyMeta->_type & SLEncodingTypeMask;
            switch (type) {
                case SLEncodingTypeObject:{
                    id value = [aDecoder decodeObjectForKey:propertyMeta->_name];
                    ((void (*)(id, SEL, id))(void *) objc_msgSend)((id)self, propertyMeta->_setter, value);
                }
                    break;
                case SLEncodingTypeSel:{
                    NSString *str = [aDecoder decodeObjectForKey:propertyMeta->_name];
                    if ([str isKindOfClass:[NSString class]]) {
                        SEL sel = NSSelectorFromString(str);
                        ((void (*)(id, SEL, SEL))(void *) objc_msgSend)((id)self, propertyMeta->_setter, sel);
                    }
                }
                    break;
                case SLEncodingTypeStruct:
                case SLEncodingTypeUnion:{
                    if (propertyMeta->_isKVCCompatible) {
                        @try {
                            NSValue *value = [aDecoder decodeObjectForKey:propertyMeta->_name];
                            if (value) {
                                [self setValue:value forKey:propertyMeta->_name];
                            }
                        } @catch (NSException *exception) {}
                    }
                }
                    break;
                default:
                    break;
            }
        }
    }
    
    return self;
}

- (NSUInteger)sl_modelHash {
    if (self == (id)kCFNull) {
        return [self hash];
    }
    
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:self.class];
    if (modelMeta->_nsType) {
        return [self hash];
    }
    
    NSUInteger value = 0;
    NSUInteger count = 0;
    for (_SLModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
        if (!propertyMeta->_isKVCCompatible) {
            continue;
        }
        
        value ^= [[self valueForKey:NSStringFromSelector(propertyMeta->_getter)] hash];
        count++;
    }
    
    if (count == 0) {
        value = (long)((__bridge void *)self);
    }
    
    return value;
}

- (BOOL)sl_modelIsEqual:(id)model {
    if (self == model) {
        return YES;
    }
    
    if (![model isMemberOfClass:self.class]) {
        return NO;
    }
    
    _SLModelMeta *modelMeta = [_SLModelMeta metaWithClass:self.class];
    
    if (modelMeta->_nsType) {
        return [self isEqual:model];
    }
    if ([self hash] != [model hash]) {
        return NO;
    }
    
    for (_SLModelPropertyMeta *propertyMeta in modelMeta->_allPropertyMetas) {
        if (!propertyMeta->_isKVCCompatible) {
            continue;
        }
        
        id this = [self valueForKey:NSStringFromSelector(propertyMeta->_getter)];
        id that = [model valueForKey:NSStringFromSelector(propertyMeta->_getter)];
        if (this == that) {
            continue;
        }
        if (this == nil || that == nil) {
            return NO;
        }
        if (![this isEqualToString:that]) {
            return NO;
        }
    }
    
    return YES;
}

- (NSString *)sl_modelDescription {
    return ModelDescription(self);
}

@end

@implementation NSArray (SLModel)

+ (NSArray *)sl_modelArrayWithClass:(Class)cls json:(id)json {
    if (!json) {
        return nil;
    }
    
    NSArray *arr = nil;
    NSData *jsonData = nil;
    if ([json isKindOfClass:[NSArray class]]) {
        arr = json;
    } else if ([json isKindOfClass:[NSString class]]) {
        jsonData = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
    } else if ([json isKindOfClass:[NSData class]]) {
        jsonData = jsonData;
    }
    
    if (jsonData) {
        arr = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
        if (![arr isKindOfClass:[NSArray class]]) {
            arr = nil;
        }
    }
    
    return [self sl_modelArrayWithClass:cls array:arr];
}

+ (NSArray *)sl_modelArrayWithClass:(Class)cls array:(NSArray *)array {
    if (!cls || !array) {
        return nil;
    }
    
    NSMutableArray *result = [NSMutableArray new];
    for (NSDictionary *dic in array) {
        if (![dic isKindOfClass:[NSDictionary class]]) {
            continue;
        }
        NSObject *one = [cls sl_modelWithDictionary:dic];
        if (one) {
            [result addObject:one];
        }
    }
    return result;
}

@end

@implementation NSDictionary (SLModel)

+ (NSDictionary *)sl_modelDictionaryWithClass:(Class)cls json:(id)json {
    if (!json) {
        return nil;
    }
    
    NSDictionary *dic = nil;
    NSData *jsonData = nil;
    if ([json isKindOfClass:[NSDictionary class]]) {
        dic = json;
    } else if ([json isKindOfClass:[NSString class]]) {
        jsonData = [(NSString *)json dataUsingEncoding:NSUTF8StringEncoding];
    } else if ([json isKindOfClass:[NSData class]]) {
        jsonData = json;
    }
    if (jsonData) {
        dic = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL];
        if (![dic isKindOfClass:[NSDictionary class]]) {
            dic = nil;
        }
    }
    
    return [self sl_modelDictionaryWithClass:cls dictionary:dic];
}

+ (NSDictionary *)sl_modelDictionaryWithClass:(Class)cls dictionary:(NSDictionary *)dic {
    if (!cls || !dic) {
        return nil;
    }
    NSMutableDictionary *result = [NSMutableDictionary new];
    for (NSString *key in dic.allKeys) {
        if (![key isKindOfClass:[NSString classForKeyedArchiver]]) {
            continue;
        }
        NSObject *one = [cls sl_modelWithDictionary:dic[key]];
        if (one) {
            result[key] = one;
        }
    }
    
    return result;
}

@end
